//
// GPSMapEdit
// (c) Konstantin Galichsky (kg@geopainting.com), 2002-2007
//
// Join objects as elements.
//

# include "StdAfx.h"
# include "JoinObjects.h"
# include "UndoRedo.h"
# include "Map.h"
# include "Label.h"
# include "RefreshHoles.h"
# include "Globals.h"

class CJoinObjectsAction : public CEditAction {
  public:
	CJoinObjectsAction (const std::vector<rgn_t *> & _rgns, rgn_t ** _ppRgnNew) :
		m_rgns (_rgns),
		m_pRgnNew (NULL),
		m_ppRgnNew (_ppRgnNew)
	{}

	virtual void GetName (string_t & _str) const {_str.append ("join objects");}
	virtual cu_t CanUndo () const {return cuYes;}
	virtual bool Do ();
	virtual void Undo ();
	virtual void Redo ();

  private:
	const std::vector<rgn_t *> m_rgns;
	rgn_t * m_pRgnNew;
	rgn_t ** const m_ppRgnNew;

	struct replacement_t {
		rgrestriction_t restrictionOld;
		rgrestriction_t restrictionNew;
	};
	typedef std::map<rgrestriction_t *, replacement_t> replacements_t;
	replacements_t m_replacements;
	void _ReplaceRoadInTurnRestrictions (const rgn_t * _pRoadOld, rgn_t * _pRoadNew);
};

bool CJoinObjectsAction::Do () {
	CCritSec::CLock lock (g_cs);

	//
	// Determine some of properties of the joined object.
	//

	const size_t cRgns = m_rgns.size ();
	assert (cRgns > 1);
	const rgn_t & rgn0 = * m_rgns [0];

	// Type/kind.
	const RGN_KIND Kind = static_cast<RGN_KIND> (rgn0.Kind);
	const USHORT ushType = rgn0.ushType;
	const type_desc_t * const pTD = rgn0.GetTypeDesc ();

	// Label.
	string_t strLabel;
	size_t cRgn;
	for (cRgn = 0; cRgn < cRgns; ++ cRgn) {
		const rgn_t & rgn = * m_rgns [cRgn];

		if (! rgn.strLabel.empty ()) {
			strLabel = rgn.strLabel;
			break;
		}
	}

	// Comment.
	string_t strComment;
	for (cRgn = 0; cRgn < cRgns; ++ cRgn) {
		const rgn_t & rgn = * m_rgns [cRgn];

		if (! rgn.strComment.empty () && ! (rgn.strComment == strComment)) {
			if (! strComment.empty ())
				strComment.append ("\r\n");
			strComment.append (rgn.strComment);
		}
	}

	// EndLevel.
	size_t cEndLevel = 0;
	for (cRgn = 0; cRgn < cRgns; ++ cRgn) {
		const rgn_t & rgn = * m_rgns [cRgn];

		if (rgn.uchEndLevel > cEndLevel)
			cEndLevel = rgn.uchEndLevel;
	}

	// Extra.
	rgn_extra_ptr pExtra;
	for (cRgn = 0; cRgn < cRgns; ++ cRgn) {
		const rgn_t & rgn = * m_rgns [cRgn];

		if (rgn.pExtra) {
			pExtra = rgn.pExtra;
			break;
		}
	}

	// Dir indicator.
	bool bDirIndicator = cRgns > 0;
	// Don't find.
	bool bDontFind     = cRgns > 0;
	for (cRgn = 0; cRgn < cRgns; ++ cRgn) {
		const rgn_t & rgn = * m_rgns [cRgn];
		bDirIndicator = bDirIndicator && (rgn.dwFlags & rgn_t::fDirIndicator) != 0;
		bDontFind     = bDontFind     && (rgn.dwFlags & rgn_t::fDontFind)     != 0;
	}

	//
	// Create the object.
	//

	m_pRgnNew = & g_map.CreateRgn (Kind);

	// Initialize the object.
	m_pRgnNew->dwFlags |= rgn_t::fModified;

	if (bDirIndicator)
		m_pRgnNew->dwFlags |= rgn_t::fDirIndicator;
	if (bDontFind)
		m_pRgnNew->dwFlags |= rgn_t::fDontFind;

	m_pRgnNew->strLabel  .swap (strLabel);
	m_pRgnNew->strComment.swap (strComment);
	m_pRgnNew->uchEndLevel = cEndLevel;

	m_pRgnNew->SetType (pTD, ushType, Kind);

	m_pRgnNew->pExtra.swap (pExtra);

	// Copy rgns' elements to the new one.
	for (cRgn = 0; cRgn < cRgns; ++ cRgn) {
		rgn_t & rgn = * m_rgns [cRgn];
		assert (! rgn.IsDeleted ());

		const size_t cElements = rgn.elements.size ();
		for (size_t cElement = 0; cElement < cElements; ++ cElement) {
			const rgn_element_t & element = rgn.elements [cElement];
			if (element.bDeleted)
				continue;

			// Create the element of the rgn.
			m_pRgnNew->elements.push_back (rgn_element_t ());
			rgn_element_t & elementNew = m_pRgnNew->elements.back ();

			elementNew = element;
		}

		if (m_pRgnNew->HasRGNodes ())
			_ReplaceRoadInTurnRestrictions (& rgn, m_pRgnNew);

		// Delete the original object.
		rgn.Delete (g_map);
	}

	m_pRgnNew->RefreshAttributes (g_map);

	if (m_pRgnNew->HasRGNodes ())
		g_map.bDirtyRGNodes = true;

	* m_ppRgnNew = m_pRgnNew;
	
	return true;
}

void CJoinObjectsAction::Undo () {
	CCritSec::CLock lock (g_cs);

	// Delete the new object.
	assert (m_pRgnNew);
	m_pRgnNew->Delete (g_map);

	bool bHasRGNodes = false;

	// Restore the old objects.
	const size_t cRgns = m_rgns.size ();
	for (size_t cRgn = 0; cRgn < cRgns; ++ cRgn) {
		rgn_t & rgn = * m_rgns [cRgn];

		rgn.Undelete (g_map);

		if (rgn.HasRGNodes ())
			bHasRGNodes = true;
	}

	// Restore turn restrictions.
	for (replacements_t::iterator ri = m_replacements.begin (); ri != m_replacements.end (); ++ ri)
		* ri->first = ri->second.restrictionOld;

	if (bHasRGNodes)
		g_map.bDirtyRGNodes = true;
}

void CJoinObjectsAction::Redo () {
	CCritSec::CLock lock (g_cs);

	// Restore the new object.
	assert (m_pRgnNew);
	m_pRgnNew->Undelete (g_map);

	bool bHasRGNodes = false;

	// Delete the old objects.
	const size_t cRgns = m_rgns.size ();
	for (size_t cRgn = 0; cRgn < cRgns; ++ cRgn) {
		rgn_t & rgn = * m_rgns [cRgn];

		rgn.Delete (g_map);

		if (rgn.HasRGNodes ())
			bHasRGNodes = true;
	}

	// Replace turn restrictions.
	for (replacements_t::iterator ri = m_replacements.begin (); ri != m_replacements.end (); ++ ri)
		* ri->first = ri->second.restrictionNew;

	if (bHasRGNodes)
		g_map.bDirtyRGNodes = true;
}

void CJoinObjectsAction::_ReplaceRoadInTurnRestrictions (const rgn_t * _pRoadOld, rgn_t * _pRoadNew) {
	assert (m_pRgnNew);
	const size_t cElements = m_pRgnNew->elements.size ();
	for (size_t cElement = 0; cElement < cElements; ++ cElement) {
		point_extras_t & PointExtras = m_pRgnNew->elements [cElement].pExtra->PointExtras;

		const size_t cPointExtras = PointExtras.size ();
		for (size_t c = 0; c < cPointExtras; ++ c) {
			rgnode_t * const pNode = PointExtras [c].GetRGNode ();
			if (! pNode)
				continue;

			typedef std::pair<rgrestrictions_t::iterator, rgrestrictions_t::iterator> rr_t;
			const rr_t rr = g_map.rgrestrictions.equal_range (pNode);
			for (rgrestrictions_t::iterator ir = rr.first; ir != rr.second; ++ ir) {
				rgrestriction_t & r = ir->second;
				if (r.pFromRoad != _pRoadOld && r.pToRoad != _pRoadOld)
					continue;

				// Save turn restriction replacement.
				replacements_t::iterator repi = m_replacements.find (& r);
				if (repi == m_replacements.end ()) {
					repi = m_replacements.insert (replacements_t::value_type (& r, replacement_t ())).first;
					repi->second.restrictionOld = r;
				}

				if (r.pFromRoad == _pRoadOld)
					r.pFromRoad = _pRoadNew;
				if (r.pToRoad   == _pRoadOld)
					r.pToRoad   = _pRoadNew;

				repi->second.restrictionNew = r;
			}
		}
	}
}

////////////////////////////////////////

bool IsJoinObjectsAppliable () {
	USHORT Kind;
	size_t cRgns = 0;

	const size_t cSelItems = g_Selection.size ();
	for (size_t cSelItem = 0; cSelItem < cSelItems; ++ cSelItem) {
		find_info_t & SelItem = g_Selection [cSelItem];
		if (! SelItem.pLayer)
			continue;

		const rgn_t & rgn = * SelItem.iRgn;			
		if (
			rgn.IsDeleted () || rgn.IsWTR () ||
			SelItem.cElementOrOrigin != -1
		)
			continue;

		if (rgn.Kind == rkPoint)
			continue;

		++ cRgns;
		if (cRgns == 1)
			Kind = rgn.Kind;
		else {
			if (Kind != rgn.Kind)
				return false;
		}
	}

	return cRgns >= 2;
}

void JoinSelectedObjects () {
	//
	// Prepare the list of rgns to join.
	//
	std::vector<rgn_t *> rgns;
	bool bPolygon = false;

	const size_t cSelItems = g_Selection.size ();
	for (size_t cSelItem = 0; cSelItem < cSelItems; ++ cSelItem) {
		find_info_t & SelItem = g_Selection [cSelItem];
		if (! SelItem.pLayer)
			continue;

		rgn_t & rgn = * SelItem.iRgn;			
		if (
			rgn.IsDeleted () || rgn.IsWTR () ||
			SelItem.cElementOrOrigin != -1
		)
			continue;

		if (rgn.Kind == rkPoint)
			continue;

		if (! rgns.empty ()) {
			const rgn_t & rgn0 = * rgns [0];
			if (rgn0.Kind != rgn.Kind)
				continue;
		}

		rgns.push_back (& rgn);
		bPolygon = rgn.Kind == rkPolygon;
	}

	//
	// Join them.
	//
	if (rgns.size () >= 2) {
		rgn_t * pRgnNew = NULL;
		if (PerformEditAction (new CJoinObjectsAction (rgns, & pRgnNew)) && pRgnNew && bPolygon)
			RefreshHoles (pRgnNew, true);
	}
}
